import re
import sys
from tkinter.filedialog import askopenfilename
import os
import win32com.client as win32
from openpyxl import load_workbook
import pandas as pd
from math import log10, floor
from collections import Counter
import pythoncom

# 常见 EIA 电容编码列表（可根据实际需求扩展）
COMMON_EIA_CODES = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
                    110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
                    120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
                    130, 222, 223, 224, 225, 226, 227, 228, 229}

def convert_to_standard_xlsx(file_path):
    """
    检查文件名是否包含 '副本'，如果没有，则用 Excel 另存为标准 xlsx，并删除原文件
    返回新的文件路径
    """
    dirname, basename = os.path.split(file_path)
    name, ext = os.path.splitext(basename)

    if "副本" in name:
        # 已经是副本，直接返回原路径
        return file_path

    # 构建新文件名
    new_name = f"{name}-副本.xlsx"
    new_path = os.path.join(dirname, new_name)

    try:
        pythoncom.CoInitialize()  # 初始化COM
        excel = win32.gencache.EnsureDispatch('Excel.Application')
        excel.Visible = False
        excel.DisplayAlerts = False

        wb = excel.Workbooks.Open(file_path)
        wb.SaveAs(new_path, FileFormat=51)  # 51 = xlOpenXMLWorkbook (.xlsx)
        wb.Close()
        excel.Quit()

        # 删除原文件
        os.remove(file_path)

        print(f"已生成标准副本文件：{new_path}")
        return new_path
    except Exception as e:
        print(f"转换文件失败：{e}")
        sys.exit(1)

def import_bom_xlsx_file():
  # 读取文件
  filename=askopenfilename(filetypes=[("Excel files","*.xlsx")],title="请选择需要处理的BOM表")
  if not filename:
      print("未选择文件，直接退出")
      sys.exit(0)
  try:
      # 读取Excel文件
      filename=convert_to_standard_xlsx(filename)
      data = pd.read_excel(filename,engine='openpyxl')
      print(f"成功读取文件：{filename}")
      return data,filename  # 将文件名字也返回，用于最后的保存
  except Exception as e:
      print(f"读取文件失败：{e}")
      sys.exit(1) # 非正常退出程序

def process_dataframe(df: pd.DataFrame, rename_dict: dict, new_columns: list, column_order: list) -> pd.DataFrame: # dataframe整理函数，得到新的表格
    """
    DataFrame 处理函 数：列名替换、新增列初始化、列排序。

    参数：
        df          : 输入的 DataFrame
        rename_dict : 列名替换字典 {旧列名: 新列名}
        new_columns : 需要新增的列列表，初始为空
        column_order: 想要的列顺序列表，未提到的列排在最后

    返回：
        处理后的 DataFrame
    """

    # 1️⃣ 列名替换
    df = df.rename(columns=rename_dict)

    # 2️⃣ 新增列初始化
    for col in new_columns:
        if col not in df.columns:
            df[col] = pd.NA  # 初始化为空

    # 3️⃣ 列排序
    # a. 列表中存在的列按 column_order 排序
    ordered_cols = [col for col in column_order if col in df.columns]
    # b. 剩余列
    remaining_cols = [col for col in df.columns if col not in ordered_cols]
    # c. 合并
    final_cols = ordered_cols + remaining_cols
    df = df[final_cols]

    return df

def process_capacitors(df: pd.DataFrame, convert_to_eia: object, sort_cap_df: object)-> pd.DataFrame: # 电容处理函数
    """
        按封装分组处理电容：
        1. 每个封装分组
        2. 调用归一化函数处理规格型号列
        3. 调用排序函数对组内排序
        4. 拼接所有封装组，返回结果

        参数：
            df             : 输入 DataFrame
            cap_column     : 电容规格列名
            package_column : 封装列名
            packages       : 封装列表，如 ["0603","0805","1206"]
            normalize_func : 外部归一化函数
            sort_func      : 外部排序函数

        返回：
            拼接好的电容 DataFrame
        """
    cap_column="规格型号"
    package_column="封装"
    qty_col="数量"
    ref_col="编号"
    sorted_groups = []
    cap_result_df = pd.DataFrame()
    packages = ["0603", "0805", "1206","1210"]
    for pkg in packages:
        # 筛选同时包含'C'和当前封装的行
        mask = df[package_column].str.contains(pkg, na=False) & df[package_column].str.contains("C",na=False) & df[ref_col].str.contains("C",na=False)
        df_pkg = df[mask].copy()
        # 如果筛选结果不为空
        if not df_pkg.empty:
            df=df[~mask]
        else:
            continue
        # 统一封装
        df_pkg[package_column] = f"C{pkg}"
        # 调用归一化函数
        df_pkg[cap_column] = df_pkg.apply(
            lambda row: convert_to_eia(row[cap_column], package=row[package_column], eia_list=COMMON_EIA_CODES), axis=1)

        # 合并同类项
        df_pkg = merge_same_components(df_pkg, [cap_column, package_column], qty_col, ref_col)
        # 调用排序函数
        df_pkg = sort_cap_df(df_pkg, cap_column)

        sorted_groups.append(df_pkg)

        # 每个分组之间插入空行
        empty_row = pd.DataFrame([[""] * df_pkg.shape[1]], columns=df_pkg.columns)
        sorted_groups.append(empty_row)
        # 拼接所有封装组
        cap_result_df = pd.concat(sorted_groups, ignore_index=True)

    return cap_result_df,df # 拼接过后的电容部分

def convert_to_eia(code: str, package: str,eia_list) -> str: # 电容容量归一化子函数
    """
    单个电容编码转换，支持 pF/nF/uF + 附加信息。
    例： "10uF/35V" → "106/35V"
    """
    if not isinstance(code, str):
        return "[类型错误]"
    if eia_list is None:
        eia_list = []

    original = code.strip().lower().replace(" ", "")
    original = re.sub(r"/\d+[v]", "", original)

    # 带单位的容量
    match_unit = re.match(r'(\d*\.?\d+)([unp])', original)
    if match_unit:
        value, unit = match_unit.groups()
        value = float(value)
        multiplier = {"p": 1, "n": 1e3, "u": 1e6}
        if unit not in multiplier:
            return f"[无法识别] {code}"
        pf_value = int(round(value * multiplier[unit]))

        if pf_value < 10:
            return f"[无法识别] {code}"
        elif pf_value < 100:
            eia_code = f"{pf_value}0"
        else:
            digits = str(pf_value)
            eia_code = digits[:2] + str(len(digits[2:]))
        return eia_code

    # 纯数字
    match_num = re.match(r'^\d+$', original)
    if match_num:
        digits = int(original)
        if digits in eia_list:
            return str(digits)  # 在常见列表中，认为是EIA编码
        else:
            return f"[无法确定] {code}"

    return f"[无法确定] {code}"

def merge_same_components(df, key_cols, qty_col="数量", ref_col="编号"):
    """
    合并相同器件：数量相加，编号拼接，保留其他列的第一条，并恢复原列顺序
    """
    original_columns = df.columns.tolist()  # 保存原列顺序

    agg_dict = {qty_col: "sum", ref_col: lambda x: ",".join(x.astype(str))}
    for c in df.columns:
        if c not in key_cols + [qty_col, ref_col]:
            agg_dict[c] = "first"

    merged_df = df.groupby(key_cols, dropna=False).agg(agg_dict).reset_index()

    # 恢复原列顺序
    merged_df = merged_df[[c for c in original_columns if c in merged_df.columns]]
    return merged_df

def sort_cap_df(df: pd.DataFrame, col: str) -> pd.DataFrame: # 电容容量排序函数
    """
    按 EIA 编码的实际容量大小对 DataFrame 排序
    可以处理像 '104/25V'、'225/X7R' 这样的带附加信息的字符串
    """
    def eia_to_value(code: str) -> int:
        if not code:
            return float('inf') # 无法识别的放最后
        # 用正则提取开头连续的数字
        match = re.match(r"(\d{3})", code)
        if not match:
            return float('inf')
        eia = match.group(1)
        sig = int(eia[:2])
        power = int(eia[2])
        return sig * (10 ** power)
    # 创建临时列进行排序
    df["_eia_value"] = df[col].apply(eia_to_value)
    df_sorted = df.sort_values("_eia_value").drop(columns="_eia_value").reset_index(drop=True)
    return df_sorted

def resistor_to_ohm(res_str: str) -> float: # 电阻排序函数
    """
    将电阻字符串转换为实际欧姆值
    """
    if not res_str:
        return 0
    s = res_str.upper().replace("_","").replace("-","")
    res_str = res_str.upper()  # 防止小写 r/k/m

    # R 表示小数点
    if "R" in s:
        try:
            return float(s.replace("R", "."))
        except:
            return 0
    elif "K" in s:
        try:
            return float(s.replace("K","")) * 1e3
        except:
            return 0
    elif "M" in s:
        try:
            return float(s.replace("M","")) * 1e6
        except:
            return 0
    else:
        try:
            return float(s)
        except:
            return 0


def process_resistors(df: pd.DataFrame) -> pd.DataFrame:
    """
    处理电阻：
    1. 从 '封装' 列中提取标准封装 (0603, 0805, 1206 等)
    2. 按封装分组
    3. 组内按实际电阻值排序
    4. 拼接所有组返回新的 DataFrame
    """
    df = df.copy()

    # 提取标准封装
    standard_packages=["0603", "0805", "1206", "1210", "2010", "2512"]

    res_result_list = []
    for pkg in standard_packages:
        # 同时包含 R 和当前封装数字
        mask = df["封装"].astype(str).str.contains(pkg) & df["封装"].str.contains("R", na=False) & df["编号"].str.contains("R", na=False)
        group = df[mask].copy()

        if group.empty:
            continue

        # 统一封装名称
        group["封装"] = "R" + pkg

        # 标准化纯数字电阻
        group["规格型号"] = group["规格型号"].apply(
            lambda x: str(x).strip().upper() + "R" if str(x).isdigit() else str(x).strip().upper())

        # 合并同类项
        group = merge_same_components(group, key_cols=["规格型号"], qty_col="数量", ref_col="编号") #

        group = group.sort_values(by="规格型号", key=lambda x: x.apply(resistor_to_ohm))

        res_result_list.append(group)

        # 插入一个空行
        empty_row = pd.DataFrame([[""] * group.shape[1]], columns=group.columns)
        res_result_list.append(empty_row)
        # 从原始 df 中删除已处理行
        df = df[~mask]

        # 拼接所有组
    res_result_df = pd.concat(res_result_list, ignore_index=True)

    # 返回处理好的 df 和剩余未匹配行
    return res_result_df, df

# 接下来要做字典匹配工作，找一部字典做内容的分组

def classify_by_dict(df, dict_file,df_res,df_cap):
    """
    df: 原始 DataFrame
    dict_file: 维护的 Excel 文件路径
    name_column: Excel 表格里规格型号列名
    desc_column: Excel 表格里描述列名
    type_column: Excel 表格里分类列名
    """
    name_column="规格型号"
    desc_column="描述"
    type_column="分类"
    df = df.copy()  # 避免修改原始 df

    # 读取维护字典
    dict_df = pd.read_excel(dict_file)

    # 需要合并的列名
    qty_col = "数量"
    id_col = "编号"

    # 其他列，除了数量和编号，都以第一行为准
    other_cols = [c for c in df.columns if c not in [qty_col, id_col, "规格型号"]]

    # 按规格型号合并
    dict_df = dict_df.groupby("规格型号", as_index=False).agg(
        **{qty_col: (qty_col, "sum"),
           id_col: (id_col, lambda x: ",".join(x.astype(str))),
           **{col: (col, "first") for col in other_cols}}
    )

    # 转换成嵌套字典：key=规格型号, value={"描述":..., "分类":...}
    component_dict = dict_df.set_index(name_column).T.to_dict("dict")


    classified = {}  # 保存按分类归类的 DataFrame,同时将已有的分类加入，便于加入难以识别的电阻电容分类
    classified["电阻"]=df_res
    classified["电容"] = df_cap

    # 遍历字典里每个规格型号
    for spec_name, info in component_dict.items():
        mask = df["规格型号"].eq(spec_name)
        matched_df = df[mask].copy()

        if not matched_df.empty:
            ctype = info[type_column]  # 分类
            # 把描述写入 df 的描述列
            matched_df[desc_column] = info[desc_column]

            # 分类保存
            if ctype in classified:
                classified[ctype] = pd.concat([classified[ctype], matched_df], ignore_index=True)
            else:
                classified[ctype] = matched_df

            # 从原 df 删除已匹配行
            df = df[~mask]
        else:
            continue  # 没匹配到，跳过

    return classified, df  # classified 是字典：分类 -> DataFrame, df 是剩余未匹配行

def order_classified_df(classified: dict, desired_order: list) -> pd.DataFrame:
    final_df_list = []

    # 按顺序添加已有分类
    for cat in desired_order:
        if cat in classified:
            df_cat = classified[cat].copy()
            final_df_list.append(df_cat)
            # 插入空白行
            empty_row = pd.DataFrame([[""] * df_cat.shape[1]], columns=df_cat.columns)
            final_df_list.append(empty_row)

    # 剩余未在顺序里的分类
    remaining_cats = [c for c in classified.keys() if c not in desired_order]
    for cat in remaining_cats:
        final_df_list.append(classified[cat])

    if final_df_list:
        final_df = pd.concat(final_df_list, ignore_index=True)
    else:
        print("警告：没有匹配到任何分类，返回空DataFrame")
        final_df = pd.DataFrame()
    return final_df

def concat_with_blank(*dfs):
    """
    拼接多个 DataFrame，中间自动加一个空白行
    """
    result = pd.DataFrame(columns=dfs[0].columns)
    for df in dfs:
        if df is not None and not df.empty:
            result = pd.concat([result, df], ignore_index=True)
            # 插入空白行
            empty_row = pd.DataFrame([[""] * df.shape[1]], columns=df.columns)
            result = pd.concat([result, empty_row], ignore_index=True)
    return result

def write_to_excel_with_sheet2(file_path: str, df: pd.DataFrame, sheet_name: str = "Sheet2"):
    """
    把 df 写入指定的 sheet，如果有就覆盖，没有就创建。
    其他 sheet 不受影响。
    """
    try:
        # 尝试加载已有工作簿
        book = load_workbook(file_path)
        with pd.ExcelWriter(file_path, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer:
            # 不需要再设置 writer.book = book
            df.to_excel(writer, sheet_name=sheet_name, index=False)
    except FileNotFoundError:
        # 如果文件不存在，则新建文件
        with pd.ExcelWriter(file_path, engine="openpyxl") as writer:
            df.to_excel(writer, sheet_name=sheet_name, index=False)

def flatten_and_split(col_data):
    """
    将每格可能包含多个编号的列展平成单个编号列表
    """
    result = []
    for item in col_data.dropna().astype(str):
        # 以逗号分割，并去掉空格
        parts = [x.strip() for x in item.split(",") if x.strip()]
        result.extend(parts)
    return result


def check_designator_consistency(original_df: pd.DataFrame, final_df: pd.DataFrame,
                                 original_col: str = "Designator",
                                 final_col: str = "编号") -> bool:
    """
    检查原始表和最终表的Designator/编号列是否完全包含相同元素
    """
    # 展平原始列和最终列
    orig_list = flatten_and_split(original_df[original_col])
    final_list = flatten_and_split(final_df[final_col])

    # 用Counter统计
    orig_counter = Counter(orig_list)
    final_counter = Counter(final_list)

    # 比较
    if orig_counter == final_counter:
        print("编号列与原Designator列一致 ✅")
        return True
    else:
        missing = orig_counter - final_counter  # 原有，最终缺少
        extra = final_counter - orig_counter  # 最终多出来的
        if missing:
            print(f"缺少的编号: {dict(missing)}")
        if extra:
            print(f"多余的编号: {dict(extra)}")
        print("编号列与原Designator列不一致 ❌")
        return False
if __name__ == "__main__":
    # 调用函数读入文件，得到dataframe
    data,file_path = import_bom_xlsx_file()
    rename_dict = {  # 列名替换列表
        "Comment": "规格型号",
        "Quantity": "数量",
        "Footprint": "封装",
        "Designator": "编号"
    }
    new_columns = ["标准型号", "描述", "品牌", "注释"]  # 新增空白列
    column_order = ["规格型号", "标准型号", "数量", "封装", "描述", "品牌", "编号", "注释"]  # 列顺序
    df_data=process_dataframe(data,rename_dict,new_columns,column_order)
    # 调用函数处理df文档，得到电容分组与剩余的df
    cap_result_df,remaining_df=process_capacitors(df_data,convert_to_eia,sort_cap_df)
    # 调用函数处理df文档，得到电阻分组与剩余的df
    res_result_df,remaining_df=process_resistors(remaining_df)
    # 调用函数处理df文档，得到剩余的分组与最后的df
    classified, remaining_df = classify_by_dict(remaining_df, "BOM元器件字典.xlsx",res_result_df,cap_result_df)
    # 定义类型的排列顺序
    desired_order = ["电容","电阻","二极管", "电感", "芯片", "电源模块", "继电器","保险","接插件"]
    # 调用函数得到排序后的分类
    final_ordered_df = order_classified_df(classified,desired_order)
    # 做最后的拼接，拼接电容、电阻、分类、最后剩余的df
    final_df = concat_with_blank( final_ordered_df, remaining_df)
    # 调用函数写入源文件的sheet2
    write_to_excel_with_sheet2(file_path, final_df, sheet_name="Sheet2")
    print("Excel 写入完成 ✅")
    # 检查目标文件与源文件的编号是否一致
    check_designator_consistency(data, final_df)










